/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package connector

import (
	"sync"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/api"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/client/inoderesolver"
	servicerDispatcher "git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/service/dispatcher"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/context"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	"github.com/pkg/errors"
)

var logger = logging.NewLogger("ichainsdk/ichain")

// Connector 选择合适的节点连接事件服务；派发区块、合约等事件
type Connector struct {
	*servicerDispatcher.Dispatcher // 各类事件的注册函数
	params
	context                context.Client
	chConfig               ichain.ChainCfg // 链配置
	connection             api.Connection  // 连接对象
	connectionRegistration *ConnectionReg
	connectionProvider     api.ConnectionProvider  // 连接生成器
	discoveryService       ichain.DiscoveryService // 服务发现
	inodeResolver          inoderesolver.Resolver  // 选择节点去连接
	inode                  ichain.Inode            // 连接节点
	lock                   sync.RWMutex
}

func New(context context.Client, chConfig ichain.ChainCfg,
	discoveryService ichain.DiscoveryService, connectionProvider api.ConnectionProvider) *Connector {
	params := defaultParams(context, chConfig.ID())

	connector := &Connector{
		Dispatcher:         servicerDispatcher.New(),
		params:             *params,
		context:            context,
		chConfig:           chConfig,
		discoveryService:   discoveryService,
		connectionProvider: connectionProvider,
	}
	connector.inodeResolver = params.inodeResolverProvider(context, chConfig.ID())

	return connector
}

func (ed *Connector) Start() error {
	ed.registerHandlers()

	if err := ed.Dispatcher.Start(); err != nil {
		return errors.WithMessage(err, "error starting client event dispatcher")
	}
	return nil
}

func (ed *Connector) ChainConfig() ichain.ChainCfg {
	return ed.chConfig
}

func (ed *Connector) Connection() api.Connection {
	return ed.connection
}

func (ed *Connector) HandleStopEvent(e servicerDispatcher.Event) {
	ed.clearConnectionRegistration()
	ed.Dispatcher.HandleStopEvent(e)
}

func (ed *Connector) HandleConnectEvent(e servicerDispatcher.Event) {
	evt := e.(*ConnectEvent)

	if ed.connection != nil {
		evt.ErrCh <- nil
		return
	}

	eventch, err := ed.EventCh()
	if err != nil {
		evt.ErrCh <- err
		return
	}

	inodes, err := ed.discoveryService.GetInodes()
	if err != nil {
		evt.ErrCh <- err
		return
	}

	if len(inodes) == 0 {
		evt.ErrCh <- errors.New("no inodes to connect to")
		return
	}

	inode, err := ed.inodeResolver.Resolve(inodes)
	if err != nil {
		evt.ErrCh <- err
		return
	}

	conn, err := ed.connectionProvider(ed.context, ed.chConfig, inode)
	if err != nil {
		logger.Warnf("error creating connection: %s", err)
		evt.ErrCh <- errors.WithMessagef(err, "could not create client conn")
		return
	}

	ed.connection = conn
	ed.setConnectedInode(inode)

	go ed.connection.Receive(eventch)

	evt.ErrCh <- nil
}

func (ed *Connector) HandleDisconnectEvent(e servicerDispatcher.Event) {
	evt := e.(*DisconnectEvent)

	if ed.connection == nil {
		evt.Errch <- errors.New("connection already closed")
		return
	}

	logger.Debug("Closing connection due to disconnect event...")

	ed.connection.Close()
	ed.connection = nil
	ed.setConnectedInode(nil)

	evt.Errch <- nil
}

func (ed *Connector) HandleRegisterConnectionEvent(e servicerDispatcher.Event) {
	evt := e.(*RegisterConnectionEvent)

	// 已经注册过了
	if ed.connectionRegistration != nil {
		evt.ErrCh <- errors.New("registration already exists for connection event")
		return
	}

	ed.connectionRegistration = evt.Reg
	evt.RegCh <- evt.Reg
}

func (ed *Connector) HandleConnectedEvent(e servicerDispatcher.Event) {
	evt := e.(*ConnectedEvent)

	logger.Debugf("Handling connected event: %+v", evt)

	if ed.connectionRegistration != nil && ed.connectionRegistration.Eventch != nil {
		select {
		case ed.connectionRegistration.Eventch <- NewConnectionEvent(true, nil):
		default:
			logger.Warn("Unable to send to connection event chain.")
		}
	}
}

func (ed *Connector) HandleDisconnectedEvent(e servicerDispatcher.Event) {
	evt := e.(*DisconnectedEvent)

	logger.Debugf("Disconnecting from event server: %s", evt.Err)

	if ed.connection != nil {
		ed.connection.Close()
		ed.connection = nil
	}

	if ed.connectionRegistration != nil {
		logger.Debugf("Disconnected from event server: %s", evt.Err)
		select {
		case ed.connectionRegistration.Eventch <- NewConnectionEvent(false, evt.Err):
		default:
			logger.Warn("Unable to send to connection event chain.")
		}
	} else {
		logger.Warnf("Disconnected from event server: %s", evt.Err)
	}
}

func (ed *Connector) registerHandlers() {
	ed.RegisterHandler(&servicerDispatcher.StopEvent{}, ed.HandleStopEvent)

	// 连接事件
	ed.RegisterHandler(&ConnectEvent{}, ed.HandleConnectEvent)
	ed.RegisterHandler(&DisconnectEvent{}, ed.HandleDisconnectEvent)
	ed.RegisterHandler(&ConnectedEvent{}, ed.HandleConnectedEvent)
	ed.RegisterHandler(&DisconnectedEvent{}, ed.HandleDisconnectedEvent)
	ed.RegisterHandler(&RegisterConnectionEvent{}, ed.HandleRegisterConnectionEvent)
}

func (ed *Connector) clearConnectionRegistration() {
	if ed.connectionRegistration != nil {
		logger.Debug("Closing connection registration event chain.")
		close(ed.connectionRegistration.Eventch)
		ed.connectionRegistration = nil
	}
}

func (ed *Connector) setConnectedInode(inode ichain.Inode) {
	ed.lock.Lock()
	defer ed.lock.Unlock()
	ed.inode = inode
}

func (ed *Connector) ConnectedInode() ichain.Inode {
	ed.lock.RLock()
	defer ed.lock.RUnlock()
	return ed.inode
}
